package org.dreamwork.tools.io;

import org.dreamwork.tools.LocalUtil;
import org.dreamwork.tools.LogFactory;
import org.dreamwork.tools.UpdateInfo;
import org.dreamwork.tools.UpdateManager;
import org.dreamwork.tools.jfx.FXSplash;
import org.dreamwork.tools.jfx.FXUpdateShell;
import org.dreamwork.tools.swing.UpdateChecker;

import java.io.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Created by seth.yang on 2018/12/11
 */
public class UpdateListener {
    private final Logger logger = LogFactory.getLogger (UpdateListener.class);
    private ServerSocket server;
    private int port;
    private boolean running = true;
    private UpdateInfo info;

    private static final UpdateListener instance = new UpdateListener ();

    public static UpdateListener getInstance () { return instance; }

    public void bind () throws IOException {
        port = (int) (Math.random () * 10000 + 50000);
        server = new ServerSocket ();
        server.bind (new InetSocketAddress (InetAddress.getByName ("127.0.0.1"), port));
        logger.log (Level.FINE, "updater listen on " + server);
        LocalUtil.service.execute (()->{
            while (running) {
                try (Socket socket = server.accept ()) {
                    // data form:
                    //      action      1 byte      0 - exit normally
                    //                              1 - update
                    //                              2 - close splash
                    //                              3 - check update
                    //                              4 - get remained update resource
                    //                              5 - remove remained update resources
                    //      ui_type     1 byte      0 - cli
                    //                              1 - swing
                    //                              2 - fx
                    //                              3 - web
                    DataInputStream dis = new DataInputStream (socket.getInputStream ());
                    int action = dis.readUnsignedByte ();
                    switch (action) {
                        case 0:     // exit normally
                            System.exit (0);
                            break;
                        case 1:     // update
                            int type    = dis.readUnsignedByte ();
                            String base = dis.readUTF ();
                            update (type, base);
                            break;
                        case 2:     // close splash
                            FXSplash.close ();
                            break;
                        case 3:     // check update
                            // data form: String category, String version, String url, String base
                            String category = dis.readUTF ();
                            String version  = dis.readUTF ();
                            String url      = dis.readUTF ();
                            info = UpdateManager.check (category, version, url);
                            if (info == null) {
                                socket.getOutputStream ().write (0);
                            } else {
                                DataOutputStream dos = new DataOutputStream (socket.getOutputStream ());
                                dos.write (1);
                                dos.writeUTF (info.targetVersion);
                            }
                            socket.getOutputStream ().flush ();
                            break;
                        case 4:     // get remained update resource
                            getRemainedUpdateResources (socket.getOutputStream ());
                            break;
                        case 5:
                            removeRemainedUpdateResources ();
                            break;
                    }
                } catch (Exception ex) {
                    ex.printStackTrace ();
                    logger.log (Level.WARNING, ex, String::new);
                }
            }
        });
    }

    public void shutdown () {
        running = false;
        if (server != null) {
            try {
                server.close ();
            } catch (IOException e) {
                e.printStackTrace ();
            }
        }
    }

    public int getPort () {
        return port;
    }

    private void update (int type, String base/*, String category, String version, String url, String base*/) {
        if (!UpdateManager.isTaskRunning ()) {
            UpdateManager.startUpdateTask (info, base);
        }

        switch (type) {
            case 0 :    // cli
                break;
            case 1:     // swing
                new UpdateChecker ();
                break;
            case 2:     // jfx
                try {
                    FXUpdateShell.start ();
                } catch (Exception e) {
                    e.printStackTrace ();
                }
                break;
            case 3:     // web
                break;
        }
    }

    private void getRemainedUpdateResources (OutputStream out) throws IOException {
        List<UpdateInfo.UpdateItem> scripts = UpdateManager.getScripts ();
        if (scripts != null && !scripts.isEmpty ()) {
            DataOutputStream dos = new DataOutputStream (out);
            dos.writeByte (scripts.size ());
            dos.writeUTF (info.targetVersion);
            for (UpdateInfo.UpdateItem item : scripts) {
                dos.writeUTF (item.type);
                dos.writeUTF (item.name);
                dos.writeUTF (item.downloadedFile);
            }
        } else {
            out.write (0);
        }
    }

    private void removeRemainedUpdateResources () {
        UpdateManager.removeScripts ();
        info = null;
    }
}